#pragma once

#include <string>
#include <mutex>
#include <vector>
#include <cassert>
#include <fstream>
#include <algorithm>

#include <jsoncpp/json/json.h>

#include "../commen/httplib.h"
#include "../commen/log.hpp"
#include "../commen/util.hpp"
#include "./oj_model_mysql.hpp"
#include "./oj_view.hpp"

namespace ns_control
{
    // 提供编译运行服务的主机
    class machine
    {
    public:
        std::string _ip;   // 提供服务的IP
        uint16_t _port;    // 提供服务的port
        uint32_t _load;    // 提供服务的机器的负载
        std::mutex *_pmtx; // 因为mutex禁止拷贝,所以这里使用指针

    public:
        machine() : _ip(""), _port(0), _load(0), _pmtx(nullptr) {}

        ~machine() {}

    public:
        // 提升主机负载
        void increase_load()
        {
            if (_pmtx != nullptr)
            {
                _pmtx->lock();
                ++_load;
                _pmtx->unlock();
            }
        }

        // 减少主机负载
        void decrease_load()
        {
            if (_pmtx != nullptr)
            {
                _pmtx->lock();
                --_load;
                _pmtx->unlock();
            }
        }

        // 重置主机负载
        void reset_load()
        {
            if (_pmtx != nullptr)
            {
                _pmtx->lock();
                _load = 0;
                _pmtx->unlock();
            }
        }

        // 获取主机负载
        uint32_t get_load()
        {
            uint32_t load = 0;
            if (_pmtx != nullptr)
            {
                _pmtx->lock();
                load = _load;
                _pmtx->unlock();
            }

            return load;
        }
    };

    static const std::string service_machines_path = "./conf/service_machines.conf";

    // 负载均衡模块
    class load_balancing
    {

    public:
        load_balancing()
        {
            assert(load_service_machines_conf(service_machines_path));

            ns_log::LOG(INFO) << "加载 " << service_machines_path
                              << " 成功!" << std::endl;
        }

        ~load_balancing() {}

    public:
        bool load_service_machines_conf(const std::string &service_machines_path)
        {
            std::ifstream in(service_machines_path);
            if (!in.is_open())
            {
                ns_log::LOG(FATAL) << "加载 " << service_machines_path
                                   << " 失败!" << std::endl;
                return false;
            }

            std::string line;
            while (getline(in, line))
            {
                std::vector<std::string> tokens;
                ns_util::string_util::split_string(line, &tokens, ":");
                if (tokens.size() != 2)
                {
                    ns_log::LOG(WARNING) << "切分 " << line << " 失败" << std::endl;
                    continue;
                }

                machine m;
                m._ip = tokens[0];
                m._port = (uint16_t)atoi(tokens[1].c_str());
                m._load = 0;
                // 因为mutex禁止拷贝,所以当初设计为指针的原因指针
                m._pmtx = new std::mutex();

                _online.push_back(_machines.size());
                _machines.push_back(m);
            }

            in.close();
            return true;
        }

        // id: 输出型参数
        // m : 输出型参数
        bool intelligent_selection(int *id, machine **m)
        {
            // 负载均衡的算法有
            // 1. 随机数+hash
            // 2. 轮询+hash
            // 我们选择第二种进行实现
            _mtx.lock();

            int online_num = _online.size();
            if (online_num == 0)
            {
                _mtx.unlock();
                ns_log::LOG(FATAL) << "所有的后端编译运行的主机都已离线,请进行维护!" << std::endl;
                return false;
            }

            // 通过遍历的方式,找到所有机器中负载最小的那一个
            *id = _online[0];
            *m = &_machines[_online[0]];
            uint32_t min_load = _machines[_online[0]].get_load();
            int min_load_index = 0;

            for (int i = 1; i < online_num; ++i)
            {
                uint32_t cur_load = _machines[_online[i]].get_load();
                if (min_load > cur_load)
                {
                    min_load = cur_load;
                    min_load_index = i;
                }
            }

            *id = _online[min_load_index];
            *m = &_machines[_online[min_load_index]];

            _mtx.unlock();
            return true;
        }

        void offline_machine(int which)
        {
            _mtx.lock();

            // 从在线列表转移到离线列表
            for (std::vector<int>::iterator iter = _online.begin();
                 iter != _online.end(); ++iter)
            {
                // 找到离线的主机
                if (*iter == which)
                {
                    // 离线的主机,需要进行负载的重置,不然会影响重新上线负载的判断
                    _machines[which].reset_load();
                    _online.erase(iter);
                    _offline.push_back(which);

                    // 因为break的存在，暂时不考虑迭代器失效的问题
                    break;
                }
            }

            _mtx.unlock();
        }

        void online_machines()
        {
            // 程序收到我们发送的信号的时候,我们将所有离线列表统一进行上线
            _mtx.lock();
            _online.insert(_online.end(), _offline.begin(), _offline.end());
            _offline.erase(_offline.begin(), _offline.end());
            _mtx.unlock();

            ns_log::LOG(INFO) << "所有的主机有上线!" << std::endl;
        }

        void show_machines()
        {
            _mtx.lock();

            std::cout << "当前在线主机列表: ";
            for (int id : _online)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;

            std::cout << "当前离线主机列表: ";
            for (int id : _offline)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;

            _mtx.unlock();
        }

    private:
        // 可以给我们提供编译运行服务的所有主机
        // 我们可以用在线主机的id,作为这个数组的下标
        // 这样就有了一个映射的关系,知道在线主机的id,就能找到这个主机对应的全部信息
        std::vector<machine> _machines;
        // 所有在线主机的id
        std::vector<int> _online;
        // 所有离线主机的id
        std::vector<int> _offline;
        // load_balancing的数据安全
        std::mutex _mtx;
    };

    class control
    {
    private:
        ns_model::model _model;
        ns_view::view _view;
        load_balancing _load_blance;

    public:
        control() {}

        ~control() {}

    public:
        void recovery_machines()
        {
            _load_blance.online_machines();
        }

        // 根据题目数据构建网页
        // html::输出型参数
        bool get_all_questions(std::string *html)
        {
            bool ret = true;

            std::vector<ns_model::question> all_questions;
            if (_model.get_all_questions(&all_questions))
            {
                // 将获取的题目按照number进行排序进行排序
                std::sort(all_questions.begin(), all_questions.end(), [](const ns_model::question &ques1, const ns_model::question &ques2) -> bool
                          { return ques1.number < ques2.number; });

                // 获取题目信息成功,将所有的题目数据构建成网页
                _view.all_questions_expand_html(all_questions, html);
            }
            else
            {
                (*html) = "获取题目失败,形成题目列表失败!";
                ret = false;
            }

            return ret;
        }

        bool get_one_question(int number, std::string *html)
        {
            bool ret = true;

            ns_model::question ques;
            if (_model.get_one_question(number, &ques))
            {
                // 获取指定题目信息成功,将指定的题目数据构建成网页
                _view.one_question_expand_html(ques, html);
            }
            else
            {
                (*html) = "指定题目: " + std::to_string(number) + "获取失败!";
                ret = false;
            }

            return ret;
        }

        void judge(int number, const std::string &in_json, std::string *out_json)
        {
            // 根据题目编号,直接拿到对应的题目细节
            ns_model::question ques;
            _model.get_one_question(number, &ques);

            // in_json进行反序列化,得到用户提交的源代码和input
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            std::string code = in_value["code"].asString();

            // 重新拼接用户代码+测试用例代码,形成新的代码
            Json::Value compile_code;
            compile_code["input"] = in_value["input"].asString();
            compile_code["code"] = code + "\n" + ques.tail;
            compile_code["cpu_limit"] = ques.cpu_limit;
            compile_code["mem_limit"] = ques.mem_limit;

            Json::FastWriter writer;
            std::string compile_string = writer.write(compile_code);

            // 选择负载最低的主机
            // 一直进行选择,知道找到可用主机,否则,就是提供服务的所有的主机全部挂掉了
            while (true)
            {
                int id = 0;
                // 因为已经有了machine的实体了,所以我们只需要获取到他的指针,就行了
                machine *m = nullptr;

                // 没有智能选择到合适的主机,跳出循环不在进行寻找
                // 说明此时,所有主机已经离线了
                if (!_load_blance.intelligent_selection(&id, &m))
                {
                    break;
                }

                // 找到对应的主机,发起对应的http请求,得到用户代码编译运行后的结果
                httplib::Client clt(m->_ip, m->_port);
                // 发起http请求,增加对应主机的负载
                m->increase_load();

                if (auto res = clt.Post("/compile_run", compile_string, "application/json;charset=utf-8"))
                {
                    ns_log::LOG(INFO) << "选择主机成功,主机id为: " << id
                                      << " 主机IP为: " << m->_ip << " 主机端口为: " << m->_port
                                      << " 当前主机的负载为: " << m->get_load() << std::endl;

                    // 将用户代码编译运行后的结果得到的结果复制给out_json
                    if (res->status == 200)
                    {
                        // 我们只处理返回的状态码为200的结果
                        *out_json = res->body;
                        m->decrease_load();
                        ns_log::LOG(INFO) << "请求服务成功!" << std::endl;
                        break;
                    }

                    // 这里也相当于一个http请求完毕了,所以减少一个负载
                    // 到达这里的原因可能是因为状态码不是我们想要的
                    // 也只有这里才会符合继续循环的条件
                    m->decrease_load();
                }
                else
                {
                    // http请求失败
                    ns_log::LOG(ERROR) << "请求主机失败,可能主机已经离线,主机id为: " << id
                                       << " 主机IP为: " << m->_ip << " 主机端口为: " << m->_port
                                       << std::endl;

                    // 将请求失败的主机加入离线列表
                    _load_blance.offline_machine(id);

                    // 显示当前在线和离线的主机
                    _load_blance.show_machines();
                }
            }
        }
    };
}